/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License");  you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * http//www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 * the specific language governing rights and limitations under the License.
 *
 * The Original Code is ART OWL API.
 *
 * The Initial Developer of the Original Code is University of Roma Tor Vergata.
 * Portions created by University of Roma Tor Vergata are Copyright (C) 2009.
 * All Rights Reserved.
 *
 * The ART OWL API were developed by the Artificial Intelligence Research Group
 * (art.uniroma2.it) at the University of Roma Tor Vergata
 * Current information about the ART OWL API can be obtained at 
 * http://art.uniroma2.it/owlart
 *
 */

package it.uniroma2.art.owlart.models;

import it.uniroma2.art.owlart.exceptions.ModelAccessException;
import it.uniroma2.art.owlart.exceptions.ModelCreationException;
import it.uniroma2.art.owlart.exceptions.ModelUpdateException;
import it.uniroma2.art.owlart.exceptions.UnsupportedRDFFormatException;
import it.uniroma2.art.owlart.exceptions.VocabularyInitializationException;
import it.uniroma2.art.owlart.io.RDFFormat;
import it.uniroma2.art.owlart.model.ARTResource;
import it.uniroma2.art.owlart.model.ARTURIResource;
import it.uniroma2.art.owlart.models.conf.BadConfigurationException;
import it.uniroma2.art.owlart.models.conf.ModelConfiguration;
import it.uniroma2.art.owlart.utilities.RDFIterators;
import it.uniroma2.art.owlart.vocabulary.OWL;
import it.uniroma2.art.owlart.vocabulary.RDF;
import it.uniroma2.art.owlart.vocabulary.RDFS;
import it.uniroma2.art.owlart.vocabulary.SKOS;
import it.uniroma2.art.owlart.vocabulary.SKOSXL;
import it.uniroma2.art.owlart.vocabulary.XmlSchema;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;


/**
 * a standard implementation of {@link ModelFactory} which takes the duty of loading basic vocabularies of the
 * RDF family (RDF, RDFS, OWL) according to the specific type of model which is being loaded
 * 
 * @author Armando Stellato
 * 
 */
public class OWLArtModelFactory<MC extends ModelConfiguration> implements ModelFactory<MC> {
	
	private ModelFactory<MC> modelFactoryImpl;

	private Set<Object> initializationSet;

	/**
	 * basic constructor which encapsulates a {@link ModelFactory} plain implementation and adds vocabulary
	 * management to it
	 * 
	 * @param mf
	 */
	private OWLArtModelFactory(ModelFactory<MC> mf) {
		this.modelFactoryImpl = mf;
		initializationSet = new HashSet<Object>();
	}

	/**
	 * basic factory which creates an instance of this class by encapsulating a {@link ModelFactory} plain
	 * implementation and adding vocabulary management to it
	 * 
	 * @param mf
	 * @return
	 */
	public static <MC extends ModelConfiguration> OWLArtModelFactory<MC> createModelFactory(
			ModelFactory<MC> mf) {
		return new OWLArtModelFactory<MC>(mf);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * it.uniroma2.art.owlart.models.ModelFactory#closeModel(it.uniroma2.art.owlart.models.BaseRDFTripleModel)
	 */
	public void closeModel(BaseRDFTripleModel rep) throws ModelUpdateException {
		modelFactoryImpl.closeModel(rep);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.uniroma2.art.owlart.models.ModelFactory#loadRDFBaseModel(java.lang.String, java.lang.String,
	 * boolean)
	 */
	public <MCImpl extends MC> BaseRDFTripleModel loadRDFBaseModel(String baseuri,
			String repositoryDirectory, MCImpl conf) throws ModelCreationException {
		BaseRDFTripleModel md = modelFactoryImpl.loadRDFBaseModel(baseuri, repositoryDirectory, conf);
		try {
			post_initialize(md, baseuri, repositoryDirectory);
		} catch (ModelUpdateException e) {
			throw new ModelCreationException(e);
		}
		return md;
	}

	public <MCImpl extends MC> BaseRDFTripleModel loadRDFBaseModel(String baseuri, String repositoryDirectory)
			throws ModelCreationException {
		try {
			return loadRDFBaseModel(baseuri, repositoryDirectory, createModelConfigurationObject());
		} catch (UnsupportedModelConfigurationException e) {
			throw new ModelCreationException(e);
		} catch (UnloadableModelConfigurationException e) {
			throw new ModelCreationException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.uniroma2.art.owlart.models.ModelFactory#loadRDFModel(java.lang.String, java.lang.String,
	 * boolean)
	 */
	public <MCImpl extends MC> RDFModel loadRDFModel(String baseuri, String repositoryDirectory, MCImpl conf)
			throws ModelCreationException {
		RDFModel md = modelFactoryImpl.loadRDFModel(baseuri, repositoryDirectory, conf);
		try {
			if (!initializationSet.contains(XmlSchema.Res.class))
				XmlSchema.Res.initialize(md);
			if (!initializationSet.contains(RDF.Res.class))
				RDF.Res.initialize(md);
			if (!initializationSet.contains(RDFS.Res.class))
				RDFS.Res.initialize(md);
			post_initialize(md, baseuri, repositoryDirectory);

		} catch (VocabularyInitializationException e) {
			throw new ModelCreationException(e);
		} catch (ModelUpdateException e) {
			throw new ModelCreationException(e);
		}

		return md;
	}

	public <MCImpl extends MC> RDFModel loadRDFModel(String baseuri, String repositoryDirectory)
			throws ModelCreationException {
		try {
			return loadRDFModel(baseuri, repositoryDirectory, createModelConfigurationObject());
		} catch (UnsupportedModelConfigurationException e) {
			throw new ModelCreationException(e);
		} catch (UnloadableModelConfigurationException e) {
			throw new ModelCreationException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.uniroma2.art.owlart.models.ModelFactory#loadRDFSModel(java.lang.String, java.lang.String,
	 * boolean)
	 */
	public <MCImpl extends MC> RDFSModel loadRDFSModel(String baseuri, String repositoryDirectory, MCImpl conf)
			throws ModelCreationException {
		RDFSModel md = modelFactoryImpl.loadRDFSModel(baseuri, repositoryDirectory, conf);
		try {
			if (!initializationSet.contains(XmlSchema.Res.class))
				XmlSchema.Res.initialize(md);
			if (!initializationSet.contains(RDF.Res.class))
				RDF.Res.initialize(md);
			if (!initializationSet.contains(RDFS.Res.class))
				RDFS.Res.initialize(md);
			post_initialize(md, baseuri, repositoryDirectory);

		} catch (VocabularyInitializationException e) {
			throw new ModelCreationException(e.getCause());
		} catch (ModelUpdateException e) {
			throw new ModelCreationException(e);
		}

		return md;
	}

	public RDFSModel loadRDFSModel(String baseuri, String repositoryDirectory) throws ModelCreationException {
		try {
			return loadRDFSModel(baseuri, repositoryDirectory, createModelConfigurationObject());
		} catch (UnsupportedModelConfigurationException e) {
			throw new ModelCreationException(e);
		} catch (UnloadableModelConfigurationException e) {
			throw new ModelCreationException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.uniroma2.art.owlart.models.ModelFactory#loadOWLModel(java.lang.String, java.lang.String,
	 * boolean)
	 */
	public <MCImpl extends MC> OWLModel loadOWLModel(String baseuri, String repositoryDirectory, MCImpl conf)
			throws ModelCreationException {
		OWLModel md = modelFactoryImpl.loadOWLModel(baseuri, repositoryDirectory, conf);
		try {
			if (!initializationSet.contains(XmlSchema.Res.class))
				XmlSchema.Res.initialize(md);
			if (!initializationSet.contains(RDF.Res.class))
				RDF.Res.initialize(md);
			if (!initializationSet.contains(RDFS.Res.class))
				RDFS.Res.initialize(md);
			if (!initializationSet.contains(OWL.Res.class))
				OWL.Res.initialize(md);
			post_initialize(md, baseuri, repositoryDirectory);

		} catch (VocabularyInitializationException e) {
			throw new ModelCreationException(e);
		} catch (ModelUpdateException e) {
			throw new ModelCreationException(e);
		}

		return md;
	}

	public <MCImpl extends MC> OWLModel loadOWLModel(String baseuri, String repositoryDirectory)
			throws ModelCreationException {
		try {
			return loadOWLModel(baseuri, repositoryDirectory, createModelConfigurationObject());
		} catch (UnsupportedModelConfigurationException e) {
			throw new ModelCreationException(e);
		} catch (UnloadableModelConfigurationException e) {
			throw new ModelCreationException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.uniroma2.art.owlart.models.ModelFactory#loadSKOSModel(java.lang.String, java.lang.String,
	 * boolean)
	 */
	public <MCImpl extends MC> SKOSModel loadSKOSModel(String baseuri, String persistenceDirectory,
			MCImpl conf) throws ModelCreationException {
		SKOSModel skosModel = modelFactoryImpl.loadSKOSModel(baseuri, persistenceDirectory, conf);
		try {
			if (!initializationSet.contains(XmlSchema.Res.class))
				XmlSchema.Res.initialize(skosModel);
			if (!initializationSet.contains(RDF.Res.class))
				RDF.Res.initialize(skosModel);
			if (!initializationSet.contains(RDFS.Res.class))
				RDFS.Res.initialize(skosModel);
			if (!initializationSet.contains(OWL.Res.class))
				OWL.Res.initialize(skosModel);
			if (!initializationSet.contains(SKOS.Res.class))
				SKOS.Res.initialize(skosModel);
			post_initialize(skosModel, baseuri, persistenceDirectory);

		} catch (VocabularyInitializationException e) {
			throw new ModelCreationException(e);
		} catch (ModelUpdateException e) {
			throw new ModelCreationException(e);
		}
		return skosModel;
	}

	public SKOSModel loadSKOSModel(String baseuri, String repositoryDirectory) throws ModelCreationException {
		try {
			return loadSKOSModel(baseuri, repositoryDirectory, createModelConfigurationObject());
		} catch (UnsupportedModelConfigurationException e) {
			throw new ModelCreationException(e);
		} catch (UnloadableModelConfigurationException e) {
			throw new ModelCreationException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.uniroma2.art.owlart.models.ModelFactory#loadSKOSXLModel(java.lang.String, java.lang.String,
	 * boolean)
	 */
	public <MCImpl extends MC> SKOSXLModel loadSKOSXLModel(String baseuri, String persistenceDirectory,
			MCImpl conf) throws ModelCreationException {
		SKOSXLModel skosModel = modelFactoryImpl.loadSKOSXLModel(baseuri, persistenceDirectory, conf);
		try {
			if (!initializationSet.contains(XmlSchema.Res.class))
				XmlSchema.Res.initialize(skosModel);
			if (!initializationSet.contains(RDF.Res.class))
				RDF.Res.initialize(skosModel);
			if (!initializationSet.contains(RDFS.Res.class))
				RDFS.Res.initialize(skosModel);
			if (!initializationSet.contains(OWL.Res.class))
				OWL.Res.initialize(skosModel);
			if (!initializationSet.contains(SKOS.Res.class))
				SKOS.Res.initialize(skosModel);
			if (!initializationSet.contains(SKOSXL.Res.class))
				SKOSXL.Res.initialize(skosModel);
			post_initialize(skosModel, baseuri, persistenceDirectory);

		} catch (VocabularyInitializationException e) {
			throw new ModelCreationException(e);
		} catch (ModelUpdateException e) {
			throw new ModelCreationException(e);
		}
		return skosModel;
	}

	public SKOSXLModel loadSKOSXLModel(String baseuri, String repositoryDirectory)
			throws ModelCreationException {
		try {
			return loadSKOSXLModel(baseuri, repositoryDirectory, createModelConfigurationObject());
		} catch (UnsupportedModelConfigurationException e) {
			throw new ModelCreationException(e);
		} catch (UnloadableModelConfigurationException e) {
			throw new ModelCreationException(e);
		}
	}

	@SuppressWarnings("unchecked")
	public <T extends RDFModel> T loadModel(Class<T> modelClass, String baseuri, String persistenceDirectory,
			ModelConfiguration conf) throws ModelCreationException {
		if (modelClass == SKOSXLModel.class)
			return (T) loadSKOSXLModel(baseuri, persistenceDirectory, (MC) conf);
		else if (modelClass == SKOSModel.class)
			return (T) loadSKOSModel(baseuri, persistenceDirectory, (MC) conf);
		else if (modelClass == OWLModel.class)
			return (T) loadOWLModel(baseuri, persistenceDirectory, (MC) conf);
		else if (modelClass == RDFSModel.class)
			return (T) loadRDFSModel(baseuri, persistenceDirectory, (MC) conf);
		else if (modelClass == RDFModel.class)
			return (T) loadRDFModel(baseuri, persistenceDirectory, (MC) conf);
		else
			throw new IllegalArgumentException(modelClass + " is an unknown class for creating an RDF model!");
	}

	public <T extends RDFModel> T loadModel(Class<T> modelClass, String baseuri, String persistenceDirectory)
			throws ModelCreationException {
		try {
			return loadModel(modelClass, baseuri, persistenceDirectory, createModelConfigurationObject());
		} catch (UnsupportedModelConfigurationException e) {
			throw new ModelCreationException(e);
		} catch (UnloadableModelConfigurationException e) {
			throw new ModelCreationException(e);
		}
	}

	/**
	 * this method takes care of specific post-initialization issues after a model has been loaded.<br/>
	 * It is invoked by the various loadMethods available in this class<br/>
	 * Currently, it just sets the baseURI for the loaded ontology.
	 * 
	 * @param rep
	 * @param baseuri
	 * @param repositoryDirectory
	 * @throws ModelUpdateException
	 */
	private void post_initialize(BaseRDFTripleModel rep, String baseuri, String repositoryDirectory)
			throws ModelUpdateException {
		rep.setBaseURI(baseuri);
	}

	/**
	 * gets the first available model configuration from the loaded ModelFactory implementation
	 * 
	 * @return
	 * @throws UnsupportedModelConfigurationException
	 * @throws UnloadableModelConfigurationException
	 */
	public MC createModelConfigurationObject() throws UnsupportedModelConfigurationException,
			UnloadableModelConfigurationException {
		return createModelConfigurationObject(modelFactoryImpl.getModelConfigurations().iterator().next());
	}

	/**
	 * this method is a short cut for the two calls:
	 * <ul>
	 * <li>{@link #createModelConfigurationObject(Class)}: with class set to the first ModelConfiguration
	 * available for the loaded ModelFactory implementation</li>
	 * <li>{@link ModelConfiguration#loadParameters(File)}: invoked from the modelConfiguration created on the
	 * previous call, on the propertyFile passed as argument of this method</li>
	 * </ul>
	 * 
	 * @param propertyFile
	 * @return
	 * @throws UnsupportedModelConfigurationException
	 * @throws BadConfigurationException
	 * @throws IOException
	 * @throws UnloadableModelConfigurationException
	 */
	public MC createModelConfigurationObject(File propertyFile)
			throws UnsupportedModelConfigurationException, BadConfigurationException, IOException,
			UnloadableModelConfigurationException {
		MC mConf = createModelConfigurationObject(modelFactoryImpl.getModelConfigurations().iterator().next());
		mConf.loadParameters(propertyFile);
		return mConf;
	}

	public <MCImpl extends MC> MCImpl createModelConfigurationObject(Class<MCImpl> mcclass)
			throws UnsupportedModelConfigurationException, UnloadableModelConfigurationException {
		return modelFactoryImpl.createModelConfigurationObject(mcclass);
	}

	public <MCImpl extends MC> MCImpl createModelConfigurationObject(Class<MCImpl> mcclass, File propertyFile)
			throws UnsupportedModelConfigurationException, UnloadableModelConfigurationException,
			BadConfigurationException, IOException {
		MCImpl mcfg = createModelConfigurationObject(mcclass);
		mcfg.loadParameters(propertyFile);
		return mcfg;
	}

	public Collection<Class<? extends MC>> getModelConfigurations() {
		return modelFactoryImpl.getModelConfigurations();
	}

	
	public static void checkVocabularyData(BaseRDFTripleModel rep, String vocNS, URL vocDataURL) throws ModelAccessException, IOException, ModelUpdateException, UnsupportedRDFFormatException {
		ARTURIResource voc = rep.createURIResource(vocNS);
		Set<ARTResource> ngs = RDFIterators.getSetFromIterator(rep.listNamedGraphs());
		if (!ngs.contains(voc)) {			
			rep.addRDF(vocDataURL, vocNS, RDFFormat.RDFXML, voc);
		}
	}
	
	
}
